using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// Wraps generic <see cref="IList{T}"/> with <see cref="IItemsChangeList{T}"/> and object <see cref="IList"/> implementation.
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	public class ItemsChangeList<T> : Collection<T>, IItemsChangeList<T>, IMoveItemList<T>
	{
		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="list">
		/// Wrapped generic list - its direct changes are not notified by <see cref="ItemsChange"/>.
		/// If null, new <see cref="List{T}"/> is created and used.
		/// </param>
		/// <param name="itemValidator">See <see cref="ItemValidator"/>.</param>
		/// <exception cref="ArgumentException">An item in <paramref name="list"/> is invalid.</exception>
		public ItemsChangeList(IList<T> list, Predicate<T> itemValidator)
			: base(list == null ? new List<T>() : list)
		{
			_itemValidator = itemValidator;
			if (list != null && itemValidator != null)
			{
				foreach (T item in list)
					ValidateItem(item);
			}
		}
		/// <summary>
		/// Constructor.
		/// <see cref="ItemValidator"/> is null.
		/// </summary>
		/// <param name="list">
		/// Wrapped generic list - its direct changes are not notified by <see cref="ItemsChange"/>.
		/// If null, new <see cref="List{T}"/> is created and used.
		/// </param>
		public ItemsChangeList(IList<T> list)
			: this(list, null)
		{
		}

		#endregion

		/// <summary>
		/// Validates items when they are inserted or replaced in the list (for invalid items (validator returns false), <see cref="ArgumentException"/> is thrown).
		/// If null, all items are valid.
		/// </summary>
		public Predicate<T> ItemValidator
		{
			get { return _itemValidator; }
		}

		#region Events

		#region IItemsChangeList<T> Members

		/// <summary>
		/// Fired when one of <see cref="ListItemsChanges"/> occures.
		/// </summary>
		public event EventHandler<ListItemsChangeEventArgs<T>> ItemsChange;

		#endregion

		/// <summary>
		/// Fires <see cref="ItemsChange"/> event.
		/// </summary>
		/// <param name="e">Event arguments.</param>
		protected virtual void OnItemsChange(ListItemsChangeEventArgs<T> e)
		{
			if (ItemsChange != null)
				ItemsChange(this, e);
		}
		/// <summary>
		/// Called when new item is inserted into the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item.</param>
		/// <param name="item">Item.</param>
		protected virtual void OnItemInserted(int index, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Add, item, index));
		}
		/// <summary>
		/// Called when item is removed from the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item before it was removed.</param>
		/// <param name="item">Item (which does not need to be activated).</param>
		protected virtual void OnItemRemoved(int index, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Remove, item, index));
		}
		/// <summary>
		/// Called when item is replaced in the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item.</param>
		/// <param name="newItem">New item.</param>
		/// <param name="oldItem">Old item.</param>
		protected virtual void OnItemReplaced(int index, T newItem, T oldItem)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Replace, newItem, oldItem, index));
		}
		/// <summary>
		/// Called when item is moved in the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="newIndex">New index of item.</param>
		/// <param name="oldIndex">Old index of item.</param>
		/// <param name="item">Item.</param>
		protected virtual void OnItemMoved(int newIndex, int oldIndex, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Move, item, newIndex, oldIndex));
		}
		/// <summary>
		/// Called when the list changed dramatically (filtering, sorting, clearing, initializing, ...).
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		protected virtual void OnItemsReset()
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Reset));
		}

		#endregion

		#region IMoveItemList<T> Members

		/// <summary>
		/// Moves the item at the specified index to a new location in the list.
		/// </summary>
		/// <param name="oldIndex">Index of item to be moved.</param>
		/// <param name="newIndex">Index of new target location of the item.</param>
		/// <exception cref="ArgumentOutOfRange">
		/// <paramref name="oldIndex"/> or <paramref name="newIndex"/> is invalid.
		/// </exception>
		public void Move(int oldIndex, int newIndex)
		{
			if (oldIndex < 0 || oldIndex >= this.Count)
				throw new ArgumentOutOfRangeException("oldIndex", oldIndex, "Index is invalid.");
			if (newIndex < 0 || newIndex >= this.Count)
				throw new ArgumentOutOfRangeException("newIndex", newIndex, "Index is invalid.");
			if (oldIndex == newIndex)
				return;
			T item = this[oldIndex];
			base.RemoveItem(oldIndex);
			base.InsertItem(newIndex, item);
			OnItemMoved(newIndex, oldIndex, item);
		}

		#endregion

		/// <summary>
		/// Calls <see cref="OnItemInserted"/>.
		/// </summary>
		protected override void InsertItem(int index, T item)
		{
			ValidateItem(item);
			base.InsertItem(index, item);
			OnItemInserted(index, item);
		}
		/// <summary>
		/// Calls <see cref="OnItemRemoved"/>.
		/// </summary>
		protected override void RemoveItem(int index)
		{
			T item = this[index];
			base.RemoveItem(index);
			OnItemRemoved(index, item);
		}
		/// <summary>
		/// Calls <see cref="OnItemReplaced"/>.
		/// </summary>
		protected override void SetItem(int index, T item)
		{
			ValidateItem(item);
			T oldItem = this[index];
			base.SetItem(index, item);
			OnItemReplaced(index, item, oldItem);
		}
		/// <summary>
		/// Calls <see cref="OnItemsReset"/>.
		/// </summary>
		protected override void ClearItems()
		{
			base.ClearItems();
			OnItemsReset();
		}
		/// <summary>
		/// Validates <paramref name="item"/> with <see cref="ItemValidator"/>.
		/// </summary>
		/// <param name="item">Item</param>
		/// <exception cref="ArgumentException"><paramref name="item"/> is invalid.</exception>
		protected virtual void ValidateItem(T item)
		{
			if (this.ItemValidator != null && !this.ItemValidator(item))
				throw new ArgumentException("Item is invalid.", "item");
		}

		/// <summary>
		/// See <see cref="ItemValidator"/>.
		/// </summary>
		private Predicate<T> _itemValidator;
	}
}
